﻿using CMS6Tools.Common.Utility;
using CMS6Tools.Management.VS;
using Common.Logging;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;

namespace CMS6Tools.Management
{
    public class CMS6EnterpriseWorker
    {
        const string SlnFileName = "CMS6.sln";
        const string CMS6MvcProjectName = "Vision.Cms.Mvc";
        const string DefaultIgnoredFolderCopyPattern = Util.DefaultIgnoredFolderCopyPattern;

        #region Ctor
        public CMS6EnterpriseWorker(string sourceCodePath, string visionDllPath, string enterprisePath = null, string ignoredFolderPattern = DefaultIgnoredFolderCopyPattern)
        {
            SourceCodePath = sourceCodePath;
            VisionDLLPath = visionDllPath;
            EnterprisePath = enterprisePath;
            if (string.IsNullOrEmpty(EnterprisePath))
                EnterprisePath = Environment.CurrentDirectory;

            ZipExePath = ConfigurationManager.AppSettings["zipExe"] ?? string.Empty;
            Logger = new global::Common.Logging.Simple.NoOpLogger();
            IgnoredFolderCopyPattern = ignoredFolderPattern;
        }
        #endregion

        #region Properties
        public string IgnoredFolderCopyPattern { get; private set; }
        public string SourceCodePath { get; private set; }
        public string EnterprisePath { get; private set; }
        public string VisionDLLPath { get; private set; }
        public string ZipExePath { get; set; }
        public ILog Logger { get; set; }
        #endregion

        #region Do
        public void Do()
        {
            if (Directory.Exists(EnterprisePath))
            {
                Logger.Info("Deleting existing files ...");
                Directory.Delete(EnterprisePath, true);
            }

            Directory.CreateDirectory(EnterprisePath);

            if (!Directory.Exists(SourceCodePath))
            {
                Logger.Error(string.Format("Sorry, can't find the source code path:{0}", SourceCodePath));
                return;
            }

            if (!Directory.Exists(EnterprisePath))
            {
                Logger.Error(string.Format("Sorry, can't find the enterprise path:{0}", EnterprisePath));
                return;
            }

            // Copy file
            FileSystemUtility.CopyDirectory(SourceCodePath, EnterprisePath, Logger: Logger, excludedFolderPattern: IgnoredFolderCopyPattern);

            // Remove *.vs files
            var allVsRelatedFiles = Directory.GetFiles(EnterprisePath, "*.vs*", SearchOption.AllDirectories);
            foreach (var vsFile in allVsRelatedFiles)
                File.Delete(vsFile);

            // Remove Source Code Control
            var slnFileName = Path.Combine(EnterprisePath, SlnFileName);
            SlnFile slnFile = SlnFile.Read(slnFileName);
            var sourceCodeControlSection = slnFile.Global.Sections.Where(c => c.Type.Equals(GlobalSection.Types_TeamFoundationVersionControl)).FirstOrDefault();
            if (sourceCodeControlSection != null)
                slnFile.Global.Sections.Remove(sourceCodeControlSection);

            // Remove Not Used Projects
            #region Remove Not Used Projects
            var notUsedProjectNames = new string[]
            {
                "Vision.Api.Common"
                ,"Vision.Api.Mvc"
                ,"Vision.Api.MvcTest"
                ,"Vision.Cms.ActiveDirectory"
                ,"Vision.Cms.ActiveDirectoryServices"
                ,"Vision.Cms.CMS6Auth"
                ,"Vision.Cms.visionLive"
                ,"Vision.Cms.Common"
                ,"Vision.Cms.EntityFramework"
                ,"Vision.Cms.ObjectContainer"
                ,"Vision.Cms.MvcMigrations.CostaMesa"
                ,"Vision.Search.Api"
                ,"Vision.Search.Domain"
                ,"Vision.Search.Domain.EntityFramework"
                ,"Vision.Search.Mvc"
                ,"Vision.Search.Services"
                ,"Vision.Cms.AutoUpdate"
                ,"Vision.Cms.AutoUpdate.ServiceContracts"
                ,"Vision.Cms.BulkUpload"
                ,"Vision.Cms.BulkUpload.Web"
                ,"Vision.Cms.RazorFormatLibrary"
                ,"Vision.VCmc.Schedules"
                ,"Vision.WinServices"
                ,"Vision.WinServices.Common"
                ,"Vision.WinServices.Test"
                ,"Vision.WinServices.GUI"
                ,"Vision.VCmc.Domain"
                ,"Vision.VCmc.Domain.EntityFramework"
                ,"Vision.VCmc.Mvc"
                ,"Vision.VCmc.ServiceContracts"
                ,"Vision.VCmc.Services"
                ,"Vision.Web.Common"
                ,"Vision.Cms.Tools"
                ,"Vision.Cms.Tools.UnitTests"
                ,"Vision.Search.License.Standard"
                ,"Vision.Search.License.Enterprise"
            };

            var notUsedProjectList = new List<ProjectInfo>();

            if (notUsedProjectNames.Any())
            {
                foreach (var project in slnFile.Projects)
                {
                    if (notUsedProjectNames.Any(c => project.Name.Equals(c, StringComparison.InvariantCultureIgnoreCase)))
                    {
                        notUsedProjectList.Add(project);
                    }
                }
            }
            DeleteNotUsedProjectOrFolder(slnFile, notUsedProjectList.ToArray());
            #endregion

            // Remove Empty Solution Folder
            var deleted =false;
            do
            {
                deleted = false;
                var toBeDeletedSolutionFolders = new List<ProjectInfo>();
                var solutionFolders = slnFile.Projects.Where(c => c.TypeGUID.Equals(ProjectInfo.TypeGUIDs_Folder, StringComparison.InvariantCultureIgnoreCase)).ToList();
                if (solutionFolders.Any())
                {
                    var nestedProjectSection = slnFile.Global.Sections.FirstOrDefault(c => c.Type.Equals(GlobalSection.Types_NestedProjects, StringComparison.InvariantCultureIgnoreCase)) as NestedProjectsGlobalSection;
                    if (nestedProjectSection == null)
                        toBeDeletedSolutionFolders = solutionFolders;
                    else
                    {
                        toBeDeletedSolutionFolders = solutionFolders.Where(c => !nestedProjectSection.Projects.Any(d => d.ParentGuid.Equals(c.GUID, StringComparison.InvariantCultureIgnoreCase))).ToList();
                    }
                }
                if (toBeDeletedSolutionFolders.Any())
                    deleted = DeleteNotUsedProjectOrFolder(slnFile, toBeDeletedSolutionFolders.ToArray());
            } while (deleted);

            // Remove Not Used Folders
            var notUsedFolder = new string[] 
            {
                "CodeUpdateLog",
                "Documents",
                "SQL",
                "Licenses"
            };
            foreach (var folder in notUsedFolder)
            {
                var folderFullPath = Path.Combine(EnterprisePath, folder);
                if (Directory.Exists(folderFullPath))
                {
                    Logger.Info(string.Format("Deleting not used folder:{0}...", folder));
                    Directory.Delete(folderFullPath, true);
                    Logger.Info(string.Format("Deleted not used folder:{0}...", folder));
                }
            }

            // Remove Empty Physical Folder
            foreach (var directory in Directory.GetDirectories(EnterprisePath))
            {
                if (!Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories).Any())
                    Directory.Delete(directory, true);
            }

            // Copy Vision DLLs
            var referenceFolder = Path.Combine(EnterprisePath, "References");
            var visionReferenceFolder = Path.Combine(referenceFolder, "Vision");
            if (!Directory.Exists(visionReferenceFolder))
                Directory.CreateDirectory(visionReferenceFolder);
            Logger.Info(string.Format("Copying Vision Reference DLLs..."));
            var allDllFileNames = new Dictionary<string, Assembly>();
            foreach (var file in Directory.GetFiles(VisionDLLPath))
            {
                allDllFileNames.Add(Path.GetFileNameWithoutExtension(file), Assembly.LoadFrom(file));
                var destFile = Path.Combine(visionReferenceFolder, Path.GetFileName(file)); 
                File.Copy(file, destFile, true);
            }

            // Change DLL References (Open *.csproj file to check solution project references)
            Logger.Info(string.Format("Changing Vision Project Reference ..."));


            /*
                a.	EFCachingProvider
                Remove VMC events
                b.	EFProviderWrapperToolkit 
                Remove VMC events
                c.	EFTracingProvider 
                Remove VMC events
                d.	Vision.Cms.CMS6
                Change to:
                RD /S /Q "$(SolutionDir)License"
                xcopy "$(TargetPath)" "$(SolutionDir)License\bin\"  /S /R /Y
                xcopy "$(ProjectDir)App_Data" "$(SolutionDir)License\App_Data\" /S /R /Y
                xcopy "$(ProjectDir)Content" "$(SolutionDir)License\Content\" /S /R /Y
                e.	Vision.Cms.Mvc
                Change to 
                copy "$(SolutionDir)References\EntityFramework 6.0.2\EntityFramework.dll" "$(TargetDir)EntityFramework.dll" 
                copy "$(SolutionDir)References\EntityFramework 6.0.2\EntityFramework.SqlServer.dll" "$(TargetDir)EntityFramework.SqlServer.dll" 
                RD /S /Q "$(TargetDir)Configuration"
                copy "$(SolutionDir)References\Vision\Vision.Cms.EntityFramework.dll" "$(TargetDir)Vision.Cms.EntityFramework.dll"
              f.Vision.Cms.Schedules
                Remove VMC events.

            */

            var postEvents = new Dictionary<string, string>();
            postEvents.Add("EFCachingProvider", string.Empty);
            postEvents.Add("EFTracingProvider", string.Empty);
            postEvents.Add("EFProviderWrapperToolkit", string.Empty);

            postEvents.Add("Vision.Cms.Schedules", string.Empty);

            var cms6ProjectPostEventBuilder = new StringBuilder();
            cms6ProjectPostEventBuilder.AppendLine("RD /S /Q \"$(SolutionDir)License\"");
            cms6ProjectPostEventBuilder.AppendLine("xcopy \"$(TargetPath)\" \"$(SolutionDir)License\\bin\\\"  /S /R /Y");
            cms6ProjectPostEventBuilder.AppendLine("xcopy \"$(ProjectDir)App_Data\" \"$(SolutionDir)License\\App_Data\\\" /S /R /Y");
            cms6ProjectPostEventBuilder.AppendLine("xcopy \"$(ProjectDir)Content\" \"$(SolutionDir)License\\Content\\\" /S /R /Y");
            postEvents.Add("Vision.Cms.CMS6", cms6ProjectPostEventBuilder.ToString());

            var mvcProjectPostEventBuilder = new StringBuilder();
            mvcProjectPostEventBuilder.AppendLine("copy \"$(SolutionDir)References\\EntityFramework 6.0.2\\EntityFramework.dll\" \"$(TargetDir)EntityFramework.dll\"");
            mvcProjectPostEventBuilder.AppendLine("copy \"$(SolutionDir)References\\EntityFramework 6.0.2\\EntityFramework.SqlServer.dll\" \"$(TargetDir)EntityFramework.SqlServer.dll\"");
            mvcProjectPostEventBuilder.AppendLine("RD /S /Q \"$(TargetDir)Configuration\"");
            mvcProjectPostEventBuilder.AppendLine("copy \"$(SolutionDir)References\\Vision\\Vision.Cms.EntityFramework.dll\" \"$(TargetDir)Vision.Cms.EntityFramework.dll\"");
            postEvents.Add("Vision.Cms.Mvc", mvcProjectPostEventBuilder.ToString());

            var allProjectTypeProjects = slnFile.Projects.Where(c => c.TypeGUID.Equals(ProjectInfo.TypeGUIDs_Project, StringComparison.InvariantCultureIgnoreCase));
            foreach (var project in allProjectTypeProjects)
            {
                var isCms6MvcProject = CMS6MvcProjectName.Equals(project.Name, StringComparison.InvariantCultureIgnoreCase);
                var segmentPrefix = "";
                var projectSegmentCount = project.Path.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries).Length - 1;
                for (var i = 0; i < projectSegmentCount; i++)
                {
                    segmentPrefix += "..\\";
                }

                var projectFile = Path.Combine(EnterprisePath, project.Path);

                var changed = false;
                var xDocument = default(XDocument);
                using (var fileStream = new FileStream(projectFile, FileMode.Open))
                {
                    xDocument = XDocument.Load(fileStream);
                    var defaultNamespace = xDocument.Root.GetDefaultNamespace().NamespaceName;

                    var lastReferenceNode = xDocument.Root.Elements(XName.Get("ItemGroup", defaultNamespace)).SelectMany(c => c.Elements(XName.Get("Reference", defaultNamespace))).LastOrDefault();

                    if (lastReferenceNode == null)
                        continue;

                    var projectReferenceNodes = xDocument.Root.Elements(XName.Get("ItemGroup", defaultNamespace)).SelectMany(c => c.Elements(XName.Get("ProjectReference", defaultNamespace))).ToList();
                    foreach (var projectReferenceNode in projectReferenceNodes)
                    {
                        var referedProjectName = projectReferenceNode.Element(XName.Get("Name", defaultNamespace)).Value;
                        if (allDllFileNames.Keys.Any(c => c.Equals(referedProjectName, StringComparison.InvariantCultureIgnoreCase)))
                        {
                            projectReferenceNode.Remove();
                            var key = allDllFileNames.Where(c => c.Key.Equals(referedProjectName, StringComparison.InvariantCultureIgnoreCase)).Select(c => c.Key).First();
                            var assembly = allDllFileNames[key];

                            var newReferenceNode = new XElement(XName.Get("Reference", defaultNamespace));
                            newReferenceNode.Add(new XAttribute(XName.Get("Include"), assembly.FullName));
                            newReferenceNode.Add(new XElement(XName.Get("SpecificVersion", defaultNamespace)) { Value = bool.FalseString });
                            newReferenceNode.Add(new XElement(XName.Get("HintPath", defaultNamespace)) { Value = string.Format("{0}References\\Vision\\{1}.dll", segmentPrefix, key) });
                            newReferenceNode.Add(new XElement(XName.Get("Private", defaultNamespace)) { Value = isCms6MvcProject ? bool.TrueString : bool.FalseString });

                            lastReferenceNode.AddAfterSelf(newReferenceNode);
                            changed = true;
                        }
                    }

                    // Post Events
                    var postEventProject = postEvents.Where(c => c.Key.Equals(project.Name, StringComparison.InvariantCultureIgnoreCase)).Select(c => c.Key).FirstOrDefault();
                    if (!string.IsNullOrEmpty(postEventProject))
                    {
                        var postEvent = postEvents[postEventProject];
                        var postEventNode = xDocument.Root.Elements(XName.Get("PropertyGroup", defaultNamespace)).SelectMany(c => c.Elements(XName.Get("PostBuildEvent", defaultNamespace))).FirstOrDefault();
                        if (postEventNode != null)
                        {
                            if (string.IsNullOrEmpty(postEvent))
                                postEventNode.Remove();
                            else
                                postEventNode.Value = postEvent;
                            changed = true;
                        }
                    }
                }
                if (changed)
                {
                    xDocument.Save(projectFile);
                    Logger.Info(string.Format("Project Reference Updated: {0}", project.Name));
                }
            }

            // Remove Misc Files
            Logger.Info("Deleting Misc Files ...");
            var etcFolder = Path.Combine(EnterprisePath, @"Project\Contents\Main\etc\");
            if (Directory.Exists(etcFolder))
                Directory.Delete(etcFolder, true);
            Directory.CreateDirectory(etcFolder);

            Directory.CreateDirectory(Path.Combine(etcFolder, "RazorDlls"));

            // Create components.vxml, sitemap.xml, robots.txt
            using (var streamWriter = new StreamWriter(Path.Combine(etcFolder, "components.vxml"), false))
            {
                streamWriter.WriteLine("<?xml version=\"1.0\" encoding=\"utf-16\"?>");
                streamWriter.WriteLine("<components xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">");
                streamWriter.WriteLine("  <installed>");
                streamWriter.WriteLine("  </installed>");
                streamWriter.WriteLine("  <installLogs>");
                streamWriter.WriteLine("  </installLogs>");
                streamWriter.WriteLine("  <uninstallLogs />");
                streamWriter.WriteLine("</components>");
            }
            using (var streamWriter = new StreamWriter(Path.Combine(etcFolder, "sitemap.xml"), false))
            {
                streamWriter.Write("<?xml version=\"1.0\" encoding=\"UTF-8\"?><urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"></urlset>");
            }
            using (var streamWriter = new StreamWriter(Path.Combine(etcFolder, "robots.txt"), false))
            {
                streamWriter.WriteLine("User-agent: *");
                streamWriter.WriteLine("Disallow: /Scripts");
                streamWriter.WriteLine("Disallow: /Admin");
                streamWriter.WriteLine("Disallow: /*.asmx*");
            }

            // Update Configuration Settings
            Logger.Info("Updating Configuration settings ...");
            // web.config
            var webConfigPath = Path.Combine(EnterprisePath, @"Vision.Cms.Mvc\web.config");
            var webConfigDocument = default(XDocument);
            using (var webConfigStream = new FileStream(webConfigPath, FileMode.Open))
            {
                webConfigDocument = XDocument.Load(webConfigStream);
                var defaultNamespace = webConfigDocument.Root.GetDefaultNamespace().NamespaceName;

                var symmetricPasswordStrategyDefaultCodeKeyAppSettingEle = webConfigDocument.Root.Elements(XName.Get("appSettings", defaultNamespace))
                                                                          .Elements(XName.Get("add", defaultNamespace))
                                                                          .Where(c => "SymmetricPasswordStrategyDefaultCodeKey".Equals(c.Attribute(XName.Get("key")).Value))
                                                                          .FirstOrDefault();
                if (symmetricPasswordStrategyDefaultCodeKeyAppSettingEle != null)
                    symmetricPasswordStrategyDefaultCodeKeyAppSettingEle.Attribute("value").Value = "******";
                 
                var toBeRemovedKeys = new string[] 
                { 
                    "EnableMigration",
                    "ConnString",
                    "ArchiveTime",
                    "ImageSaveToPath",
                    "DocumentSaveToPath",
                    "ServiceRequest.DocumentSavePath",
                    "EncryptionPassword",
                    "LocalTimeDiff",
                    "ObserveDaylightSaving"
                };
                var toBeRemovedAppSettingElements = webConfigDocument.Root.Elements(XName.Get("appSettings", defaultNamespace))
                                                                          .Elements(XName.Get("add", defaultNamespace))
                                                                          .Where(c => toBeRemovedKeys.Contains(c.Attribute(XName.Get("key")).Value))
                                                                          .ToList();
                foreach (var toBeRemovedEle in toBeRemovedAppSettingElements)
                {
                    toBeRemovedEle.Remove();
                }
            }
            webConfigDocument.Save(webConfigPath);

            // custom.config
            var customConfigPath = Path.Combine(EnterprisePath, @"Project\Contents\Main\Configuration\custom.config");
            var customConfigDocument = default(XDocument);
            using(var customConfigFileStream = new FileStream(customConfigPath,FileMode.Open))
            {
                customConfigDocument = XDocument.Load(customConfigFileStream);
                var defaultNamespace = customConfigDocument.Root.GetDefaultNamespace().NamespaceName;
                var connectionStringElement = customConfigDocument.Root.Elements(XName.Get("connectionStrings", defaultNamespace)).Elements(XName.Get("add", defaultNamespace)).First();
                connectionStringElement.Attributes(XName.Get("connectionString")).First().Value = "Data Source=******;Initial Catalog=CMS60_MVC_DEV;User ID==******;Password==******;multipleactiveresultsets=true;";

                var vcmcURLAppSettingEle = customConfigDocument.Root.Elements(XName.Get("appSettings", defaultNamespace))
                                                                  .Elements(XName.Get("add", defaultNamespace))
                                                                  .Where(c => c.Attributes(XName.Get("key")).Any(d => d.Value.Equals("VCmcURL")))
                                                                  .FirstOrDefault();
                if (vcmcURLAppSettingEle != null)
                    vcmcURLAppSettingEle.Remove();
            }
            customConfigDocument.Save(customConfigPath);

            Logger.Info("Refreshing solution file ...");
            using (var streamWriter = new StreamWriter(slnFileName, false))
            {
                streamWriter.Write(slnFile.ToString());
            }
        }
        #endregion

        #region Helper Methods
        protected bool DeleteNotUsedProjectOrFolder(SlnFile slnFile, ProjectInfo[] toBeRemovedProjects, bool removePhysicalFile = true)
        {
            if (toBeRemovedProjects.Any())
            {
                var solutionFolder= Path.GetDirectoryName(slnFile.FileFullPath);
                foreach (var project in toBeRemovedProjects)
                {
                    var isFolder = project.TypeGUID.Equals(ProjectInfo.TypeGUIDs_Folder, StringComparison.InvariantCultureIgnoreCase);
                    var isProject = project.TypeGUID.Equals(ProjectInfo.TypeGUIDs_Project, StringComparison.InvariantCultureIgnoreCase);
                    Logger.Info(string.Format("Deleting {0}: {1}", isFolder ? "Project Folder" : "Project", project.Name));

                    var nestedProjectSection = slnFile.Global.Sections.FirstOrDefault(c => c.Type.Equals(GlobalSection.Types_NestedProjects, StringComparison.InvariantCultureIgnoreCase)) as NestedProjectsGlobalSection;
                    if (nestedProjectSection != null)
                    {
                        var count = nestedProjectSection.Projects.Count;
                        var allProjects = nestedProjectSection.Projects.ToList();
                        for (var index = 0; index < count; index++)
                        {
                            var proj = allProjects[index];
                            if (proj.ChildGuid.Equals(project.GUID, StringComparison.InvariantCultureIgnoreCase)
                            || proj.ParentGuid.Equals(project.GUID, StringComparison.InvariantCultureIgnoreCase))
                                nestedProjectSection.Projects.Remove(proj);
                        }
                    }

                    var projectConfigurationGlobalSection = slnFile.Global.Sections.FirstOrDefault(c => c.Type.Equals(GlobalSection.Types_ProjectConfigurationPlatforms, StringComparison.InvariantCultureIgnoreCase)) as ProjectConfigurationGlobalSection;
                    if (projectConfigurationGlobalSection != null)
                    {
                        var count = projectConfigurationGlobalSection.Projects.Count;
                        var allProjects = projectConfigurationGlobalSection.Projects.ToList();
                        for (var index = 0; index < count; index++)
                        {
                            var proj = allProjects[index];
                            if (proj.Guid.Equals(project.GUID, StringComparison.InvariantCultureIgnoreCase))
                                projectConfigurationGlobalSection.Projects.Remove(proj);
                        }
                    }


                    slnFile.Projects.Remove(project);

                    if (isFolder)
                    {
                        var folderPath = Path.Combine(solutionFolder, project.Path);
                        if (Directory.Exists(folderPath))
                            Directory.Delete(folderPath, true);
                    }
                    else if (isProject)
                    {
                        var projectFileFullPath = Path.Combine(solutionFolder, project.Path);
                        var folderPath = Path.GetDirectoryName(projectFileFullPath);
                        if (Directory.Exists(folderPath))
                            Directory.Delete(folderPath, true);
                    }

                    Logger.Info(string.Format("Deleted {0}: {1}", isFolder ? "Project Folder" : "Project", project.Name));
                }

                return true;
            }
            return false;
        }
        #endregion
    }
}
